package pCloud.obligation;

import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import pCloud.PCloudAttributeAndType;
import pCloud.ResourceConnection;
import pCloud.Utilities;
import pCloud.message.DatabaseReply;
import pCloud.message.DatabaseRequest;
import pCloud.server.DatabaseHandle;
import pCloud.service.SQLDatabaseHandle;

import com.sun.xacml.Obligation;
import com.sun.xacml.attr.IntegerAttribute;
import com.sun.xacml.attr.StringAttribute;
import com.sun.xacml.ctx.Attribute;
import com.sun.xacml.ctx.RequestCtx;

/**
 * Handle sliding-window obligation.
 * 
 * All the information necessary for enforcing this policy comes from the
 * Obligation 1. start time 2. size (in hour) of the window 3. step (how the
 * window slides)
 * 
 * Simply issues multiple queries for each window, sliding it to the next step
 * every iteration. The iteration stops when the end of the sliding window
 * exceeds the specified ending time (OBLIGATION_SLIDING_WINDOW_END).
 */

public class SlidingWindowObligation extends DataProcessingObligation {
	int hourSize, hourStep;
	String timestampColumnId;
	Calendar startingDateTime;
	Calendar endingDateTime;

	public Object processObligation(String joiningColumn, DatabaseHandle rc,
			HashMap<URI, Obligation> obligationMap, RequestCtx requestCtx)
			throws Exception {

		Obligation obligation = obligationMap.get(new URI(
				PCloudAttributeAndType.OBLIGATION_SLIDING_WINDOW_ID));
		List attributes = obligation.getAssignments();

		// 1. Extract out the sliding window start time, window size and sliding
		// step
		this.parseListOfAttributes(attributes);

		// 2. Get the select clause
		String selectClause = this.constructSelectClause(obligationMap,
				requestCtx);

		String whereClause = "";
		Calendar startingPoint = Calendar.getInstance();
		startingPoint.setTime(startingDateTime.getTime());
		startingPoint.add(Calendar.HOUR, hourSize);
		DatabaseRequest dbr;
		DatabaseReply result;

		ArrayList<DatabaseReply> results = new ArrayList<DatabaseReply>();
		String preparedString = selectClause
				+ " where samplingtime > ? and samplingtime < ?";

		PreparedStatement pst = rc.getConnection().prepareStatement(
				preparedString);
		while (startingPoint.before(endingDateTime)) {
			whereClause = " where " + this.timestampColumnId + " > '"
					+ Utilities.parseCalendarOutput(startingDateTime)
					+ "' and " + this.timestampColumnId + " < '"
					+ Utilities.parseCalendarOutput(startingPoint) + "'";

			// execute and print
			dbr = new DatabaseRequest(selectClause
					+ this.getTableId(requestCtx) + whereClause);

			// results.add(rc.evaluateRequest(joiningColumn, dbr));

			DatabaseReply dr = ((SQLDatabaseHandle) rc)
					.evaluateRepeatedRequestWithPreparedStm(joiningColumn, pst,
							Utilities.parseCalendarOutput(startingDateTime),
							Utilities.parseCalendarOutput(startingPoint));
			results.add(dr);
			// ResultSet results = (ResultSet)rc.executeQuery(selectClause +
			// tableId + whereClause);
			// printResultSet(out, results);

			startingDateTime.add(Calendar.HOUR, this.hourStep);
			startingPoint.setTime(startingDateTime.getTime());
			startingPoint.add(Calendar.HOUR, hourSize);
		}
		if (results.size() == 0)
			return null;
		else
			return this.constructDatabaseReplyFromList(joiningColumn, results);
	}

	/**
	 * When sliding window obligation is used, more than one database are
	 * returned, one for each window. This method merge them into one reply
	 * message
	 * 
	 * We assume that each response contains only one row
	 */
	private DatabaseReply constructDatabaseReplyFromList(
			String joiningColumnId, ArrayList<DatabaseReply> replies) {
		DatabaseReply firstReply = replies.get(0);
		DatabaseReply otherReply;
		Object[] keySet;
		for (int i = 1; i < replies.size(); i++) {
			otherReply = replies.get(i);
			keySet = otherReply.getRowsOfValues().keySet().toArray();
			for (int j = 0; j < keySet.length; j++) {
				firstReply.addKeyValue(i);
				firstReply.addSetOfValuesToRow(i, otherReply.getRowsOfValues()
						.get(keySet[j]));
			}

		}
		return firstReply;
	}

	private ArrayList mergeList(ArrayList list1, ArrayList list2) {
		for (int i = 0; i < list2.size(); i++)
			list1.add(list2.get(i));
		return list1;
	}

	/**
	 * Parse the attributes as reading from the policy file.
	 * 
	 * The window-size and sliding-step are of type integer
	 * 
	 * The window-starting-time is a string of the format: yyyy-mm-dd hh:mm:ss
	 */
	private void parseListOfAttributes(List atts) throws Exception {
		Attribute att;
		long iatt;
		for (int i = 0; i < atts.size(); i++) {
			att = (Attribute) atts.get(i);
			if (att.getValue() instanceof IntegerAttribute) {
				iatt = ((IntegerAttribute) att.getValue()).getValue();
				if (att.getId()
						.equals(new URI(
								PCloudAttributeAndType.OBLIGATION_ATT_WINDOW_SIZE)))
					this.hourSize = (int) iatt;
				else if (att
						.getId()
						.equals(new URI(
								PCloudAttributeAndType.OBLIGATION_ATT_WINDOW_STEP)))
					this.hourStep = (int) iatt;
				else
					throw new Exception("Unrecognized attribute " + att.getId());
			} else if (att.getValue() instanceof StringAttribute) {
				String value = ((StringAttribute) att.getValue()).getValue();
				if (att.getId()
						.equals(new URI(
								PCloudAttributeAndType.OBLIGATION_ATT_WINDOW_START))) {
					this.startingDateTime = Calendar.getInstance();
					SimpleDateFormat sdf = new SimpleDateFormat(
							"yyyy-MM-dd hh:mm:ss");
					this.startingDateTime.setTime(sdf.parse(value));
				} else if (att
						.getId()
						.equals(new URI(
								PCloudAttributeAndType.OBLIGATION_ATT_WINDOW_END))) {
					this.endingDateTime = Calendar.getInstance();
					SimpleDateFormat sdf = new SimpleDateFormat(
							"yyyy-MM-dd hh:mm:ss");
					this.endingDateTime.setTime(sdf.parse(value));
				} else if (att
						.getId()
						.equals(new URI(
								PCloudAttributeAndType.OBLIGATION_ATT_WINDOW_COLUMN))) {
					this.timestampColumnId = value;
				} else
					throw new Exception("Unrecognized attriute " + att.getId());
			}
		}
	}
}
